package gateway.filters;

import framework.config.SecurityConfig;
import framework.security.FunctionPermission;
import framework.utils.JsonUtil;
import framework.vo.R;
import gateway.config.GatewayConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import security.defined.AuthenticationCheckerResult;
import security.error.AuthenticationCheckerException;
import security.filters.AuthenticationBase;
import security.filters.AuthenticationData;
import security.filters.AuthenticationHandler;
import security.filters.FunctionPermissionHandler;
import security.service.SecurityContext;

import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;

@Slf4j
@Component
@ConditionalOnProperty(name = "sys.security.enabled", havingValue = "true", matchIfMissing = true)
public class AuthenticationWebFluxFilter extends AuthenticationBase implements GlobalFilter {
    private final HashSet<String> removePathMap = new HashSet<>();

    //
    public final String authIdentityHeaderName;

    public AuthenticationWebFluxFilter(SecurityContext securityContext, GatewayConfig gatewayConfig) {
        super(securityContext);
        log.info("Initialized");
        //
        String[] list = gatewayConfig.getAppPathList();
        if (list != null && list.length > 0) {
            for (String s : list) {
                log.info("config application path: " + s);
                if (s.endsWith("/")) {
                    removePathMap.add(s.substring(0, s.length() - 1));
                } else {
                    removePathMap.add(s);
                }
            }
        }
        //
        String identityHeader = securityContext.getSecurityConfig().getAuthIdentityHeader();
        if (!StringUtils.hasText(identityHeader)) {
            throw new IllegalArgumentException("Not configuration sys.security.auth_identity_header");
        }
        this.authIdentityHeaderName = identityHeader;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 非法检测
        HttpHeaders headers = exchange.getRequest().getHeaders();
        if (headers.containsKey(authIdentityHeaderName)) {
            // 移除非法请求
            headers.remove(authIdentityHeaderName);
        }

        // 授权
        AuthenticationData data = null;
        if (getSecurityConfig().getEnable()) {
            try {
                data = this.authCheck(this.createAuthenticationHandler(exchange, chain, getSecurityConfig()));
            } catch (AuthenticationCheckerException authenticationCheckerException) {
                if (AuthenticationCheckerResult.NO_AUTH.equals(authenticationCheckerException.getStatus())) {
                    if (StringUtils.hasText(getSecurityConfig().getRedirectOnNoAuth())) {
                        return this.redirect(exchange, getSecurityConfig().getRedirectOnNoAuth());
                    } else {
                        return this.error(exchange, HttpStatus.UNAUTHORIZED, authenticationCheckerException);
                    }
                } else if (AuthenticationCheckerResult.NO_PERMISSION.equals(authenticationCheckerException.getStatus())) {
                    if (StringUtils.hasText(getSecurityConfig().getRedirectOnNoPermission())) {
                        return this.redirect(exchange, getSecurityConfig().getRedirectOnNoPermission());
                    } else {
                        return this.error(exchange, HttpStatus.METHOD_NOT_ALLOWED, authenticationCheckerException);
                    }
                } else {
                    if (StringUtils.hasText(getSecurityConfig().getRedirectOnAuthError())) {
                        return this.redirect(exchange, getSecurityConfig().getRedirectOnAuthError());
                    } else {
                        return this.error(exchange, HttpStatus.INTERNAL_SERVER_ERROR, authenticationCheckerException);
                    }
                }
            } catch (Throwable throwable) {
                // 验证异常
                log.error("Authentication valid error," + throwable.getMessage(), throwable);
                if (StringUtils.hasText(getSecurityConfig().getRedirectOnAuthError())) {
                    return this.redirect(exchange, getSecurityConfig().getRedirectOnAuthError());
                } else {
                    return this.error(exchange, HttpStatus.UNAUTHORIZED, throwable);
                }
            }
        }
        //
        ServerHttpRequest request2;
        if (data != null) {
            request2 = exchange.getRequest().mutate()
                    .header(authIdentityHeaderName, data.toBase64())
                    .build();
        } else {
            request2 = exchange.getRequest().mutate()
                    .header(authIdentityHeaderName, "")
                    .build();
        }

        //
        return chain.filter(exchange.mutate().request(request2).build());

    }

    /**
     * 创建授权处理器
     *
     * @param exchange
     * @param chain
     * @param securityConfig
     * @return
     */
    private AuthenticationHandler createAuthenticationHandler(ServerWebExchange exchange, GatewayFilterChain chain, SecurityConfig securityConfig) {
        return new AuthenticationWebFluxHandler(exchange, chain, getSecurityConfig());
    }

    private Mono<Void> redirect(ServerWebExchange exchange, String url) {
        ServerHttpResponse response = exchange.getResponse();
        // 输导异常页面
        response.setStatusCode(HttpStatus.FOUND);
        response.getHeaders().add("Location", url);
        //
        return response.writeWith(Mono.empty());
    }

    private Mono<Void> error(ServerWebExchange exchange, HttpStatus httpStatus, Throwable throwable) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        String message = throwable.getMessage();
        if (message == null || message.equals(""))
            message = throwable.getClass().getSimpleName();

        //
        if (HttpStatus.UNAUTHORIZED.equals(httpStatus)
                && getSecurityConfig().getEnableWwwAuthenticate()
                && getSecurityConfig().getEnableBasicAuth()) {
            response.getHeaders().add("WWW-Authenticate", "Basic realm=\"" + message.replace("\"", "") + "\"");
        }

        //
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        DataBuffer buffer = response.bufferFactory().wrap(JsonUtil.toJsonString(R.failed(message)).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public List<FunctionPermission> loadFunctionPermissionList() {
        // 重载： 添加系统必要的需要授权访问的路径配置, 以下配置也可以通过菜单配置
        // 通常为系统支持性非业务性的接口或地址在此配置，业务地址通过菜单配置

        List<FunctionPermission> fpl = super.loadFunctionPermissionList();

        // --------------------
        // 需要授权才能访问的系统路径
        fpl.add(new FunctionPermission("/file/s/**", "AUTH", null));
        //我的账号信息
        fpl.add(new FunctionPermission("/my/**", "AUTH", null));

        // --------------------
        // 允许匿名访问的的系统路径
        fpl.add(new FunctionPermission("/", "ANON", null));
        fpl.add(new FunctionPermission("/favicon.ico", "ANON", null));
        fpl.add(new FunctionPermission("/robots.txt", "ANON", null));
        // 开放资源
        fpl.add(new FunctionPermission("/file/g/**", "ANON", null));
        fpl.add(new FunctionPermission("/pub/**", "ANON", null));
        // 开放静态资源
        fpl.add(new FunctionPermission("/js/**", "ANON", null));
        fpl.add(new FunctionPermission("/css/**", "ANON", null));
        fpl.add(new FunctionPermission("/img/**", "ANON", null));
        // 开放swagger
        fpl.add(new FunctionPermission("/swagger-ui/**", "ANON", null));
        fpl.add(new FunctionPermission("/swagger-resources/**", "ANON", null));
        fpl.add(new FunctionPermission("/v3/api-docs", "ANON", null));
        fpl.add(new FunctionPermission("/v2/api-docs", "ANON", null));

        // 将登入/登出路径设置为匿名访问
        if (StringUtils.hasText(getSecurityConfig().getLoginPath()))
            fpl.add(new FunctionPermission(getSecurityConfig().getLoginPath(), "ANON", null));
        if (StringUtils.hasText(getSecurityConfig().getLogoutPath()))
            fpl.add(new FunctionPermission(getSecurityConfig().getLogoutPath(), "ANON", null));

        //
        return fpl;
    }

    /**
     * 重载功能匹配器，进行应用程序目录过滤
     *
     * @param method
     * @param path
     * @return
     */
    @Override
    protected FunctionPermissionHandler findFunctionMatcher(String method, String path) {
        // 若配置有应用程序目录
        if (!removePathMap.isEmpty()) {
            int idx = path.indexOf("/", 1);
            if (idx > 0) {
                String p = path.substring(0, idx);
                if (removePathMap.contains(p)) {
                    path = path.substring(idx);
                }
            }
        }
        //
        return super.findFunctionMatcher(method, path);
    }
}

